﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Design.Kernel.IO;
using DesignKernel.CAD;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using DesignKernel.Gdi;
using Tao.OpenGl;
using Tao.Platform.Windows;
using System.Drawing;
using DesignKernel.Interfaces;
using DesignKernel.Control;

namespace Design.Kernel.Modeling
{
    /// <summary>
    /// Main modeling class
    /// </summary>
    public sealed class TModeling : IDisposable
    {
        private float fovy = 45.0f;
        private List<TModel> _models = new List<TModel>();
        private IntPtr wndDeviceContext = IntPtr.Zero;
        private IntPtr openGlRenderingContext = IntPtr.Zero;
        private IntPtr controlHandle = IntPtr.Zero;
        private UserControl usUserControlDrawOn = null;
        private List<TLightSource> lightSources = new List<TLightSource>();
        private RectangleF modelBounds = RectangleF.Empty;
        private TMatrix cameraMatrix = new TMatrix();
        int fontDisplayList = 1;


        public bool IsBoundsDirty = false;


        public bool DrawAxis = true;



        /// <summary>
        /// Initialise opengl and attached it to on the control handle specified
        /// </summary>
        private void InitOpenGl(IntPtr handle)
        {
            Gdi.PIXELFORMATDESCRIPTOR pfd = new Gdi.PIXELFORMATDESCRIPTOR();
            int iPixelFormat;

          

            pfd.nSize = (short)Marshal.SizeOf(pfd);
            pfd.nVersion = 1;
            pfd.dwFlags = Gdi.PFD_DRAW_TO_WINDOW | Gdi.PFD_SUPPORT_OPENGL | Gdi.PFD_DOUBLEBUFFER;
            pfd.iPixelType = Gdi.PFD_TYPE_RGBA;
            pfd.cColorBits = 32;
            pfd.cDepthBits = 32;
            pfd.iLayerType = Gdi.PFD_MAIN_PLANE;

            wndDeviceContext = User.GetDC(handle);

            iPixelFormat = Gdi.ChoosePixelFormat(wndDeviceContext, ref pfd);
            if (iPixelFormat == 0)
            {
                MessageBox.Show("Can not find a suitable PixelFormat.", "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Hand);
                Environment.Exit(-1);
            }


            if (!Gdi.SetPixelFormat(this.wndDeviceContext, iPixelFormat, ref pfd))
            {
                MessageBox.Show("Can not set the chosen PixelFormat.  Chosen PixelFormat was " + iPixelFormat + ".", "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Hand);
                Environment.Exit(-1);
            }           
            
            openGlRenderingContext = Wgl.wglCreateContext(wndDeviceContext);
            if(openGlRenderingContext == IntPtr.Zero) 
            {
                MessageBox.Show("Can not create OpenGL rendering context", "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Hand);
                Environment.Exit(-1);
            }


            if (!Wgl.wglMakeCurrent(wndDeviceContext, openGlRenderingContext))
            {

                MessageBox.Show("Can not activate OpenGL rendering context", "Fatal Error", MessageBoxButtons.OK, MessageBoxIcon.Hand);
                Environment.Exit(-1);
            }

            // Configure lighting
            // SetLighting();

            // Hidden surface removal
            Gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            Gl.glClearDepth(1.0f);
           
            Gl.glEnable(Gl.GL_DEPTH_TEST);
            Gl.glDepthFunc(Gl.GL_LEQUAL);
          //  Gl.glEnable(Gl.GL_COLOR_MATERIAL);
           // Gl.glColorMaterial(Gl.GL_FRONT, Gl.GL_AMBIENT_AND_DIFFUSE);

            //Gl.glEnable(Gl.GL_POLYGON_OFFSET_FILL);
            //Gl.glEnable(Gl.GL_POLYGON_OFFSET_LINE);
            //Gl.glEnable(Gl.GL_POLYGON_OFFSET_POINT);
            //Gl.glPolygonOffset(1.0f, 1.0f);

            Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST);
            Gl.glHint(Gl.GL_LINE_SMOOTH_HINT, Gl.GL_NICEST);
            Gl.glShadeModel(Gl.GL_FLAT);

            //m_backgroundDisplayList = Gl.glGenLists(1);

            //SetBackgroundColors(Color.Blue, Color.Red, Color.Green, Color.Yellow);
        }


        public void DestroyContext()
        {
            if (this.openGlRenderingContext != IntPtr.Zero)
            {
                Wgl.wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
                Wgl.wglDeleteContext(this.openGlRenderingContext);
                this.openGlRenderingContext = IntPtr.Zero;
            }
            if (this.wndDeviceContext != IntPtr.Zero)
            {
                if (this.wndDeviceContext != IntPtr.Zero)
                {
                    User.ReleaseDC(this.controlHandle, this.wndDeviceContext);
                }
                this.wndDeviceContext = IntPtr.Zero;
            }


          //  ((SceneUC)usUserControlDrawOn).DestroyContexts();
        }

        public void CenterModelsOnScene()
        {
            Bound totalBound = new Bound();
            _models.ForEach((model) =>
            {
                totalBound += model.Bounds;
            });

            CenterBoundOnScene(totalBound);
        }


        public void CenterBoundOnScene(Bound bound)
        {          
            double width = bound.GetWidth();
            double height = bound.GetHeight();
            double deepness = bound.Deepness;

            double movement = System.Math.Max(width, height);
            movement += deepness;

            double xTranslate = -(width/2) ;
            double yTranslate = -(height/2);
            cameraMatrix.Position = new TVertex { x = xTranslate, y = yTranslate, z = -movement };

        }


        public void DrawOn(UserControl uc)
        {
            if (uc == null)
                throw new ArgumentNullException("The uc parameter is Null");   

            usUserControlDrawOn = uc;

            if (usUserControlDrawOn as ISupportOpenGL == null)
                throw new ArgumentException("Control must implement ISupportOpenGL interface in order to be drawable by this framework");
          //  ((SceneUC)uc).InitializeContexts();
            //((ISupportOpenGL)usUserControlDrawOn).SetControlStyle(ControlStyles.Selectable, true);
            controlHandle = usUserControlDrawOn.Handle;
            InitOpenGl(controlHandle);

            usUserControlDrawOn.Resize += new EventHandler(usUserControlDrawOn_Resize);            
            usUserControlDrawOn.Paint += new PaintEventHandler(usUserControlDrawOn_Paint);

        }

        void usUserControlDrawOn_Resize(object sender, EventArgs e)
        {
           
            if (usUserControlDrawOn.Height == 0)
                usUserControlDrawOn.Height = 10;

            if (usUserControlDrawOn.Width == 0)
                usUserControlDrawOn.Width = 10;

            Gl.glViewport(0, 0, usUserControlDrawOn.Width, usUserControlDrawOn.Height);


            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();


           
            Glu.gluPerspective(fovy, usUserControlDrawOn.Width / usUserControlDrawOn.Height, 1.0f, 100.0f);
           
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
        }

        void usUserControlDrawOn_Paint(object sender, PaintEventArgs e)
        {
            Wgl.wglMakeCurrent(wndDeviceContext, openGlRenderingContext);
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

            CenterModelsOnScene();
            DrawAllModels(e);

            Gdi.SwapBuffers(wndDeviceContext);
            Gl.glFlush();
            return;

            Gl.glMatrixMode(Gl.GL_MODELVIEW);
           
            Gl.glLoadMatrixd(cameraMatrix.Array);
           

            float [] mtx = new float[16];
            Gl.glGetFloatv(Gl.GL_MODELVIEW_MATRIX, mtx);

            Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_LINE);

          
            Gl.glBegin(Gl.GL_TRIANGLES);



            if (_models.Count > 0)
            {
                for (int i = 0; i < _models[0].Triangles.Count; i++)
                {
                    TVector normal = _models[0].Triangles[i].GetNormal();
                    Gl.glNormal3d(normal.x, normal.y, normal.z);
                    Gl.glVertex3d(_models[0].Triangles[i].Vertex0.x, _models[0].Triangles[i].Vertex0.y, _models[0].Triangles[i].Vertex0.z);
                    Gl.glVertex3d(_models[0].Triangles[i].Vertex1.x, _models[0].Triangles[i].Vertex1.y, _models[0].Triangles[i].Vertex1.z);
                    Gl.glVertex3d(_models[0].Triangles[i].Vertex2.x, _models[0].Triangles[i].Vertex2.y, _models[0].Triangles[i].Vertex2.z);
                }

                //_models[0].Triangles.ForEach((triangle) =>
                //{
                //    TVector normal = triangle.GetNormal();
                //    Gl.glNormal3d(normal.x, normal.y, normal.z);
                //    Gl.glVertex3d(triangle.Vertex0.x, triangle.Vertex0.y, triangle.Vertex0.z);
                //    Gl.glVertex3d(triangle.Vertex1.x, triangle.Vertex1.y, triangle.Vertex1.z);
                //    Gl.glVertex3d(triangle.Vertex2.x, triangle.Vertex2.y, triangle.Vertex2.z);
                //});
            }


            Gl.glVertex3f(-1.0f, 0, 0);
            Gl.glVertex3f(0, 1.0f, 0);
            Gl.glVertex3f(0, 0, 1.8f);



            Gl.glVertex3f(-0.5f, 0, 0);
            Gl.glVertex3f(0, 0.5f, 0);
            Gl.glVertex3f(0, 0, 1.8f);

            Gl.glEnd();


            Gdi.SwapBuffers(wndDeviceContext);


            Gl.glFlush();

            
        }


        /// <summary>
        /// Register the fonts used in 
        /// </summary>
        public void RegisterUsedFonts()
        {
            Gdi.GLYPHMETRICSFLOAT[] gmf = new Gdi.GLYPHMETRICSFLOAT[256];
            Font font = new Font("Verdana",12.0f);
            Gdi.SelectObject(wndDeviceContext, font.ToHfont());
            Wgl.wglUseFontOutlines(wndDeviceContext, 0, 255, fontDisplayList, 0, 0.2f, Wgl.WGL_FONT_POLYGONS, gmf);


        }

        public void RegisterKnownFileTypes()
        {
            FileLoaderFactory.RegisterConcreteFileLoader(".stl", new StlLoader());
        }


        /// <summary>
        /// Adds model from specified file
        /// </summary>
        /// <param name="fileName">The complete file path from where the model have to be loaded</param>
        /// <param name="bVisible">If True, the model will be drawn, False otherwise</param>
        /// <returns></returns>
        public TModel AddModel(string fileName, bool bVisible)
        {
            string fileExtension = System.IO.Path.GetExtension(fileName);
            _models.Add(
                FileLoaderFactory.GetConcreteFileLoader(fileExtension).LoadFile(fileName));

            _models[_models.Count - 1].Visible = bVisible;

            return _models[_models.Count - 1];
        }


        public TModeling()
        {
            RegisterKnownFileTypes();
            RegisterUsedFonts();
        }


        /// <summary>
        /// Draws all models in collection except non visible ones
        /// </summary>
        /// <param name="pargs"></param>
        public void DrawAllModels(PaintEventArgs pargs)
        {
           
            Gl.glMatrixMode(Gl.GL_MODELVIEW);         
            Gl.glLoadMatrixd(cameraMatrix.Array);

         //   Gl.glGetFloatv(Gl.GL_MODELVIEW_MATRIX, cameraMatrix.Array);
            

            if (DrawAxis)
                DrawAxises();

            
            _models.ForEach((m) =>
            {
                if (m.Visible)
                    DrawModel(m, pargs);
            });
                      
        }

        /// <summary>
        /// Draws the model
        /// </summary>
        /// <param name="model"></param>
        /// <param name="pargs"></param>
        public void DrawModel(TModel model, PaintEventArgs pargs)
        {
           
           

            Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_LINE);
         


            if (model.ModelRendetSettings.RenderMode == RenderSettings.RenderModeEnum.Triangle)
                Gl.glBegin(Gl.GL_TRIANGLES);
            else if (model.ModelRendetSettings.RenderMode == RenderSettings.RenderModeEnum.Point)
                Gl.glBegin(Gl.GL_POINTS);


            model.Triangles.ForEach((triangle) =>
            {
                TVector normal = triangle.GetNormal();
                Gl.glNormal3d(normal.x, normal.y, normal.z);
                Gl.glVertex3d(triangle.Vertex0.x, triangle.Vertex0.y, triangle.Vertex0.z);
                Gl.glVertex3d(triangle.Vertex1.x, triangle.Vertex1.y, triangle.Vertex1.z);
                Gl.glVertex3d(triangle.Vertex2.x, triangle.Vertex2.y, triangle.Vertex2.z);
            });

            Gl.glEnd();

        }

        private void DrawAxises()
        {
           

            float fLineWitdh = 1.0f;
            float fLineLength = 11.0f;
            float fLetterScale = 0.8f;


            Gl.glPushAttrib(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_LIGHTING_BIT);
           
            Gl.glEnable(Gl.GL_BLEND);
            Gl.glLightModeli(Gl.GL_LIGHT_MODEL_TWO_SIDE, Gl.GL_TRUE);
            Gl.glDisable(Gl.GL_CULL_FACE);
            Gl.glLineWidth(fLineWitdh);
            Gl.glDisable(Gl.GL_LINE_STIPPLE);

            Gl.glBegin(Gl.GL_LINES);
            
            //X + 
            Gl.glColor3f(1.0f, 0.0f, 0.0f);
            Gl.glVertex3d(0, 0, 0);
            Gl.glVertex3d(fLineLength, 0, 0);
            
            //Y+
            Gl.glColor3f(0.0f, 1.0f, 0.0f);
            Gl.glVertex3d(0, 0, 0);
            Gl.glVertex3d(0, fLineLength, 0);
           
            //Z+
            Gl.glColor3f(0.0f, 0.0f, 1.0f);
            Gl.glVertex3d(0, 0, 0);
            Gl.glVertex3d(0, 0, fLineLength);
            Gl.glEnd();

            Gl.glLineStipple(1, 0xAA);
            Gl.glEnable(Gl.GL_LINE_STIPPLE);

            Gl.glBegin(Gl.GL_LINES);
           
            Gl.glVertex3d(0, 0, 0);
            Gl.glVertex3d(-fLineLength, 0, 0);
         
            Gl.glVertex3d(0, 0, 0);
            Gl.glVertex3d(0, -fLineLength, 0);
          
            Gl.glVertex3d(0, 0, 0);
            Gl.glVertex3d(0, 0, -fLineLength);
            Gl.glEnd();

            double size = fLetterScale * fLineLength / 25;


            Draw3DText(TVertex.NegativeXDefaultVertex,  0, size, Color.Yellow, 1, "-X", 1.0f);
            Draw3DText(TVertex.PositiveXDefaultVertex, 0, size, Color.Yellow, 1, "X", 1.0f);
            Draw3DText(TVertex.NegativeYDefaultVertex, 0, size, Color.Yellow, 1, "-Y", 1.0f);
            Draw3DText(TVertex.PositiveYDefaultVertex, 0, size, Color.Yellow, 1, "Y", 1.0f);
            Draw3DText(TVertex.NegativeZDefaultVertex, 0, size, Color.Yellow, 1, "-Z", 1.0f);
            Draw3DText(TVertex.PositiveZDefaultVertex, 0, size, Color.Yellow, 1, "Z", 1.0f);

         
            Gl.glPopAttrib();
        }


        private void Draw3DText(TVertex vertex, int fontNr, double size, Color color, float transparency, string text,float scale)
        {

            if (string.IsNullOrEmpty(text))
                throw new ArgumentNullException("text parameter is null or empty");

            byte[] bytes = ASCIIEncoding.ASCII.GetBytes(text);

            Gl.glPushMatrix();
            Gl.glTranslated(vertex.x, vertex.y, vertex.z);

        
        
            Gl.glPushAttrib(Gl.GL_LIST_BIT | Gl.GL_POLYGON_BIT);
            Gl.glListBase(fontDisplayList);

            Gl.glCallLists(text.Length, Gl.GL_UNSIGNED_BYTE, bytes);
            Gl.glPopAttrib();
            Gl.glPopMatrix();
        }

        public void Dispose()
        {
            DestroyContext();
        }
    }
}
